This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.
Try executing a chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.
Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.
When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).
The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.
The code should be executed in order.
install.packages("tidyverse")
Error in install.packages : Updating loaded packages
library(tidyverse)
read in data
clarks <- read.csv("data/Data\ Science\ Assessment.csv")
summarise data
summary(clarks)
X cust.id age credit.score email distance.to.store online.visits online.trans
Min. : 1 Min. : 1 Min. :13.54 Min. :504.4 no :70054 Min. : 0.0306 Min. : 0.00 Min. : 0.000
1st Qu.: 25001 1st Qu.: 25001 1st Qu.:31.60 1st Qu.:689.9 yes:29946 1st Qu.: 3.2743 1st Qu.: 0.00 1st Qu.: 0.000
Median : 50000 Median : 50000 Median :34.97 Median :724.8 Median : 7.3183 Median : 7.00 Median : 2.000
Mean : 50000 Mean : 50000 Mean :34.98 Mean :724.9 Mean : 14.9791 Mean : 30.16 Mean : 9.048
3rd Qu.: 75000 3rd Qu.: 75000 3rd Qu.:38.35 3rd Qu.:759.9 3rd Qu.: 16.4615 3rd Qu.: 34.00 3rd Qu.: 10.000
Max. :100000 Max. :100000 Max. :56.33 Max. :941.2 Max. :1886.2149 Max. :1046.00 Max. :298.000
online.spend store.trans store.spend sat.service sat.selection
Min. : 0.00 Min. : 0.000 Min. : 0.00 Min. :1.0 Min. :1.00
1st Qu.: 0.00 1st Qu.: 0.000 1st Qu.: 0.00 1st Qu.:3.0 1st Qu.:2.00
Median : 40.47 Median : 1.000 Median : 30.29 Median :3.0 Median :3.00
Mean : 182.70 Mean : 1.319 Mean : 47.17 Mean :3.1 Mean :2.81
3rd Qu.: 202.27 3rd Qu.: 2.000 3rd Qu.: 65.83 3rd Qu.:4.0 3rd Qu.:3.00
Max. :6781.67 Max. :26.000 Max. :1332.51 Max. :5.0 Max. :5.00
NA's :34933 NA's :34933
Note: Max age is very young - ignoring the fastest growing demographic in UK (the ageing baby boomers).
Very high extremes for many variables, heavilly skewed data
check for missing data - only missing for the satisfaction score
missing.values <- clarks %>%
gather(key = "key", value = "val") %>%
mutate(is.missing = is.na(val)) %>%
group_by(key, is.missing) %>%
summarise(num.missing = n()) %>%
filter(is.missing==T) %>%
select(-is.missing) %>%
arrange(desc(num.missing))
attributes are not identical across measure variables;
they will be dropped
missing.values
missing.values %>%
ggplot() +
geom_bar(aes(x=key, y=num.missing), stat = 'identity') +
labs(x='variable', y="number of missing values", title='Number of missing values') +
theme(axis.text.x = element_text(angle = 45, hjust = 1))

Add some new columns
clarks <- mutate(clarks,
total.spend = online.spend + store.spend,
total.trans = online.trans + store.trans)
clarks <- mutate(clarks,
prop.spend.online = online.spend / total.spend,
prop.trans.online = online.trans / total.trans)
clarks <- mutate(clarks,
online.spend.per.trans = online.spend / online.trans,
store.spend.per.trans = store.spend / store.trans,
total.spend.per.trans = total.spend / total.trans)
In store spending accounts for 20% of total
total_online = sum(clarks$online.spend)
total_store = sum(clarks$store.spend)
total_store/(total_online+total_store)
[1] 0.2052013
profile customer spending in store or online
clarks$online.profile <- "Mixed"
clarks$online.profile[clarks$online.spend == 0] <- "Store only"
clarks$online.profile[clarks$store.spend == 0] <- "Online only"
clarks$online.profile[clarks$total.spend == 0] <- "No spend"
Characteristics of online profile including “No Spend” - no correlation with age, credit, satisfaction. Distance is interesting - it looks like the No Spenders and the Online only people live further away.
clarks %>%
group_by(online.profile) %>%
summarise(no_rows = length(cust.id))
clarks %>%
group_by(online.profile) %>%
summarise(mean_spend = mean(total.spend))
clarks %>%
group_by(online.profile) %>%
summarise(mean_age = mean(age))
clarks %>%
group_by(online.profile) %>%
summarise(mean_credit = mean(credit.score))
clarks %>%
group_by(online.profile) %>%
summarise(median.distance = median(distance.to.store))
filter(clarks, sat.service != "NA") %>%
group_by(online.profile) %>%
summarise(mean.sat.ser = mean(sat.service))
filter(clarks, sat.selection != "NA") %>%
group_by(online.profile) %>%
summarise(mean.sat.sel = mean(sat.selection))
Data subset of only customers who actually spend something
clarks.spend <- filter(clarks , online.profile != "No spend")
Are there any duplicate customers who may be same person on and offline?
Check duplicates in distance.to.store? - only one and they have different age so all good.
length(unique(clarks$distance.to.store))
[1] 99999
n_occur <- data.frame(table(clarks$distance.to.store))
clarks[clarks$distance.to.store %in% n_occur$Var1[n_occur$Freq > 1],]
First look at correlations: Plot shows positive correlations in red and negative in blue. Bigger circles with stroger colour indicate stronger corelations.
data.numeric <- mutate(clarks, numeric.email = as.numeric(email=="yes"))
rquery.cormat(data.numeric[,c(3,4,6,7,8,9,10,11,12,13,15)])
$r
$p
$sym
age credit.score distance.to.store sat.service sat.selection online.spend online.visits online.trans total.trans store.trans
age 1
credit.score 1
distance.to.store 1
sat.service 1
sat.selection . 1
online.spend 1
online.visits B 1
online.trans B B 1
total.trans B B 1 1
store.trans 1
store.spend +
store.spend
age
credit.score
distance.to.store
sat.service
sat.selection
online.spend
online.visits
online.trans
total.trans
store.trans
store.spend 1
attr(,"legend")
[1] 0 ‘ ’ 0.3 ‘.’ 0.6 ‘,’ 0.8 ‘+’ 0.9 ‘*’ 0.95 ‘B’ 1

online spend, visits and transactions are strongly positively correlates store transactions and spend also
satisfaction with service and selection positively correlated age and credit score loosely corelated (positive)
weak negative correlation between distance to store and store spend and treansactions
very weak negative correlation between age and online visit/trans/spend (older people slightly less likely to shop online)
total transaction is far more linked to online spend - probably as store spend in only 20% of total
Online profile
Online only people seem to spend a lot less PER TRANSACTION and within a much smaller range (e.g. for shoes only buy one pair at a time) - incentivise buy one get one 20% off as they add to cart?
clarks.spend %>%
ggplot( aes(x = online.profile, y = total.spend.per.trans, group = online.profile)) +
geom_jitter( alpha=0.05 , colour = "red" ) +
geom_boxplot( colour = "blue" , alpha=0)

pivot_longer(clarks.spend, cols = store.spend.per.trans:online.spend.per.trans, names_to = "where", values_to = "ave.spend.per.trans") %>%
ggplot(aes(x = where,y = ave.spend.per.trans), group = where) +
geom_jitter( alpha=0.05 , colour = "red" ) +
geom_boxplot( colour = "blue" , alpha=0)

Store only people tend not to spend as much OVERALL - can we get them to be returning customers? Offer in bag with purchase? For returning to store or online? Collect email addresses in store? Do we already ask? How high is take up rate?
clarks.spend %>%
ggplot( aes(x = online.profile, y = total.spend, group = online.profile)) +
guides(colour = guide_legend(override.aes = list(alpha = 1)))+
geom_jitter( alpha=0.05 , colour = "red" ) +
geom_boxplot( colour = "blue" , alpha=0)

select(clarks.spend, store.spend,online.spend)%>%
pivot_longer( cols = 1:2, names_to = "where", values_to = "spend") %>%
ggplot(aes(x = where,y = spend), group = where) +
geom_jitter( alpha=0.05 , colour = "red" ) +
geom_boxplot( colour = "blue" , alpha=0)

Email is slightly lower for store only, but actually quite low throughout - perhaps we can encourage email sharing and collect data to see how successful this is. Competition or offer to get people to sign up?

Look at satisfaction
This is not very correlated to anything other than the two metrics being correlated with each other
clarks.spend %>%
ggplot( aes(x = online.profile, y = sat.selection, group = online.profile)) +
guides(colour = guide_legend(override.aes = list(alpha = 1)))+
geom_jitter(alpha = 0.01 , colour = "blue" ) +
geom_boxplot(alpha = 0)

How do they have satisfaction scores for no spend people?
clarks[clarks$sat.service != "NA",] %>%
ggplot(aes(online.profile)) +
geom_bar(aes(fill = as.factor(sat.service)), position = "fill")

Age
rr ggplot(data = clarks.spend, aes(x = age, y = total.spend, colour = online.profile)) + geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1))) + scale_y_continuous(trans=‘log2’)

rr
ggplot(data = clarks.spend, aes(x = age, y = total.spend.per.trans, colour = online.profile)) +
geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1)))

It seems there is little relationship between age and TOTAL SPEND, but old and young spend less PER TRANSACTION. Perhaps those in 30s buy for their whole family at once but can’t budget more overall. Or they have less time to shop so buy all at once.
rr
filter(clarks.spend , online.profile != \Store only\) %>%
ggplot(aes(x = age, y = total.spend.per.trans, colour = online.profile)) +
geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1)))

Credit score
rr
ggplot(data = clarks.spend, aes(x = credit.score, y = total.spend, colour = online.profile)) +
geom_point(alpha = 0.1)+ guides(colour = guide_legend(override.aes = list(alpha = 1))) +
scale_y_continuous(trans='log2')

rr
ggplot(data = clarks.spend, aes(x = credit.score, y = total.spend.per.trans, colour = online.profile)) +
geom_point(alpha = 0.1)+ guides(colour = guide_legend(override.aes = list(alpha = 1))) +
scale_y_continuous(trans='log2')

distance to store
rr
filter(clarks.spend, distance.to.store < 0.99* max(distance.to.store)) %>%
ggplot( aes(x = distance.to.store, y = total.spend, colour = online.profile)) +
geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
scale_x_continuous(trans='log10') + scale_y_continuous(trans='log2')

Can see what you would expect here - those closer to store more likely to spend in store. those far away more likely to shop online only.
Below are lots more pots which may be of interest in future with different data but did not yield much insight
rr
ggplot(data = clarks, aes(x = online.visits, y = total.spend, colour = online.profile)) +
geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1)))

rr
ggplot(data = clarks, aes(x = online.visits, y = online.spend, colour = online.profile)) +
geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1)))

rr
ggplot(data = clarks, aes(x = online.visits, y = store.spend, colour = online.profile)) +
geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1)))

ggplot(data = clarks, aes(x = store.trans, y = store.spend, colour = prop.spend.online)) +
geom_point(alpha = 0.1)

ggplot(data = clarks, aes(x = store.trans, y = online.spend, colour = prop.spend.online)) +
geom_point(alpha = 0.1)

ggplot(data = clarks, aes(x = store.trans, y = total.spend, colour = prop.spend.online)) +
geom_point(alpha = 0.1)

Are older people more satisfied?
clarks %>%
mutate(age.group=cut(age, breaks=c(0, 20, 30, 40, 50, 100), labels=c("<20","20s","30s","40s","50s"))) %>%
group_by(age.group) %>%
filter(sat.service != "NA") %>%
summarize(mean.satisfaction = mean(sat.service))
Are older people spending less per transaction? I did look this way, but actually just fewer data points in these categories.
rr
clarks %>%
mutate(age.group=cut(age, breaks=c(0, 20, 30, 40, 50, 100), labels=c(\<20\,\20s\,\30s\,\40s\,\50s\))) %>%
ggplot( aes(x = age.group, y = total.spend.per.trans, group = age.group, colour = online.profile)) +
guides(colour = guide_legend(override.aes = list(alpha = 1)))+
geom_jitter(alpha = 0.1 ) +
geom_boxplot(alpha = 0)

Are older people spending more money?
clarks %>%
mutate(age.group=cut(age, breaks=c(0, 20, 30, 40, 50, 100), labels=c("<20","20s","30s","40s","50s"))) %>%
ggplot( aes(x = age.group, y = total.spend, group = age.group, colour = online.profile)) +
guides(colour = guide_legend(override.aes = list(alpha = 1)))+
geom_jitter(alpha = 0.1 ) +
geom_boxplot(alpha = 0)

Are older people spending more frequently?
clarks.spend %>%
mutate(age.group=cut(age, breaks=c(0, 20, 30, 40, 50, 100), labels=c("<20","20s","30s","40s","50s"))) %>%
ggplot( aes(x = age.group, y = total.trans, group = age.group, colour = online.profile)) +
guides(colour = guide_legend(override.aes = list(alpha = 1)))+
geom_jitter(alpha = 0.1 ) +
geom_boxplot(alpha = 0)

# Satisfaction as a input?
clarks %>%
group_by(sat.service) %>%
summarise(no_rows = length(cust.id))
clarks %>%
group_by(sat.service) %>%
summarise(mean_spend = mean(total.spend))
clarks %>%
group_by(sat.service) %>%
summarise(mean_age = mean(age))
clarks %>%
group_by(sat.service) %>%
summarise(mean_credit = mean(credit.score))
clarks %>%
group_by(sat.service) %>%
summarise(median.distance = median(distance.to.store))
filter(clarks, sat.selection != "NA") %>%
group_by(sat.service) %>%
summarise(mean.sat.sel = mean(sat.selection))
There doesn’t appear to be any correlation between satisfaction or service and any other variables, except for satisfaction of selection which you would expect high scorers would score high on both
Are more satisfied people making more purchases?
filter(clarks, sat.service != "NA") %>%
ggplot( aes(x = sat.service, y = total.trans, group = sat.service, colour = online.profile)) +
guides(colour = guide_legend(override.aes = list(alpha = 1)))+
geom_jitter(alpha = 0.1 ) +
geom_boxplot(alpha = 0)

Are more satisfied people spending more?
rr
filter(clarks, sat.service != \NA\) %>%
ggplot( aes(x = sat.service, y = total.spend, group = sat.service, color = prop.spend.online)) +
geom_jitter(alpha = 0.1 ) +
geom_boxplot(alpha = 0)
rr
filter(clarks, sat.selection != \NA\) %>%
ggplot( aes(x = sat.selection, y = total.spend, group = sat.selection, color = prop.spend.online)) +
geom_jitter(alpha = 0.1 ) +
geom_boxplot(alpha = 0)
rr
filter(clarks, sat.service != \NA\) %>%
filter( !is.nan(prop.spend.online)) %>%
ggplot( aes(x = prop.spend.online, y = sat.service)) +
geom_point(alpha = 0.1 )
rr
filter(clarks, sat.service != \NA\) %>%
ggplot( aes(x = sat.service, y = prop.spend.online, group = sat.service)) +
geom_jitter(alpha = 0.05 ) +
geom_violin(alpha = 0, colour = \red\)
rr
filter(clarks, sat.selection != \NA\) %>%
ggplot( aes(x = sat.selection, y = prop.spend.online, group = sat.selection)) +
geom_jitter(alpha = 0.05 ) +
geom_violin(alpha = 0, colour = \red\)
rr clarks%>% group_by(sat.selection)%>% summarize(mean.spend = mean(total.spend))%>% ggplot( aes(x=sat.selection, y=mean.spend)) + geom_col()

rr
clarks%>%
group_by(sat.service)%>%
summarize(mean.spend = mean(total.spend))%>%
ggplot( aes(x=sat.service, y=mean.spend)) +
geom_col()
rr
filter(clarks, sat.selection != \NA\) %>%
ggplot( aes(x = sat.selection)) +
geom_bar( )
rr
filter(clarks, sat.selection != \NA\) %>%
ggplot( aes(x = sat.service)) +
geom_bar( )
rr
clarks%>%
mutate(no.online.visits = online.visits==0) %>%
ggplot( aes(x = no.online.visits, y = total.spend, group = no.online.visits, colour = sat.service)) +
geom_jitter(alpha = 0.05 ) +
geom_violin(alpha = 0, colour = \red\)
rr
clarks.spend%>%
filter(online.profile != \None\) %>%
ggplot( aes(x = online.profile, y = total.spend, group = online.profile, colour = sat.service)) +
geom_jitter(alpha = 0.05 ) +
geom_violin(alpha = 0, colour = \red\)
rr
clarks.spend%>%
filter(online.profile != \None\) %>%
ggplot( aes(x = online.profile, y = distance.to.store, group = online.profile, colour = sat.service)) +
geom_jitter(alpha = 0.05 ) +
geom_violin(alpha = 0, colour = \red\)
rr
clarks%>%
filter(store.spend>0)%>%
ggplot( aes(x = distance.to.store, y = store.spend, colour = sat.service)) +
geom_point(alpha=0.1)
rr
clarks%>%
mutate(store = store.spend > 0)%>%
group_by(store) %>%
summarize(mean_spend = mean(online.spend))
rr
clarks%>%
mutate(store = store.spend > 0)%>%
ggplot(aes(x=store, y=total.spend , group=store))+
geom_violin()
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayAtIENsYXJrcyBpbnRlcnZpZXcgYXNzaWdubWVudCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIGEgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ3RybCtTaGlmdCtFbnRlciouIAoKQWRkIGEgbmV3IGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqSW5zZXJ0IENodW5rKiBidXR0b24gb24gdGhlIHRvb2xiYXIgb3IgYnkgcHJlc3NpbmcgKkN0cmwrQWx0K0kqLgoKV2hlbiB5b3Ugc2F2ZSB0aGUgbm90ZWJvb2ssIGFuIEhUTUwgZmlsZSBjb250YWluaW5nIHRoZSBjb2RlIGFuZCBvdXRwdXQgd2lsbCBiZSBzYXZlZCBhbG9uZ3NpZGUgaXQgKGNsaWNrIHRoZSAqUHJldmlldyogYnV0dG9uIG9yIHByZXNzICpDdHJsK1NoaWZ0K0sqIHRvIHByZXZpZXcgdGhlIEhUTUwgZmlsZSkuCgpUaGUgcHJldmlldyBzaG93cyB5b3UgYSByZW5kZXJlZCBIVE1MIGNvcHkgb2YgdGhlIGNvbnRlbnRzIG9mIHRoZSBlZGl0b3IuIENvbnNlcXVlbnRseSwgdW5saWtlICpLbml0KiwgKlByZXZpZXcqIGRvZXMgbm90IHJ1biBhbnkgUiBjb2RlIGNodW5rcy4gSW5zdGVhZCwgdGhlIG91dHB1dCBvZiB0aGUgY2h1bmsgd2hlbiBpdCB3YXMgbGFzdCBydW4gaW4gdGhlIGVkaXRvciBpcyBkaXNwbGF5ZWQuCgpUaGUgY29kZSBzaG91bGQgYmUgZXhlY3V0ZWQgaW4gb3JkZXIuCgpgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikgIAppbnN0YWxsLnBhY2thZ2VzKCJjb3JycGxvdCIpCmBgYAoKCmBgYHtyfQpsaWJyYXJ5KCJ0aWR5dmVyc2UiKQpsaWJyYXJ5KCJjb3JycGxvdCIpCmBgYAoKcmVhZCBpbiBkYXRhCmBgYHtyfQpjbGFya3MgPC0gcmVhZC5jc3YoImRhdGEvRGF0YVwgU2NpZW5jZVwgQXNzZXNzbWVudC5jc3YiKQpgYGAKCnN1bW1hcmlzZSBkYXRhCmBgYHtyfQpzdW1tYXJ5KGNsYXJrcykKYGBgCgojIE5vdGU6IE1heCBhZ2UgaXMgdmVyeSB5b3VuZyAtIGlnbm9yaW5nIHRoZSBmYXN0ZXN0IGdyb3dpbmcgZGVtb2dyYXBoaWMgaW4gVUsgKHRoZSBhZ2VpbmcgYmFieSBib29tZXJzKS4KIyBWZXJ5IGhpZ2ggZXh0cmVtZXMgZm9yIG1hbnkgdmFyaWFibGVzLCBoZWF2aWxseSBza2V3ZWQgZGF0YQoKCgpjaGVjayBmb3IgbWlzc2luZyBkYXRhIC0gb25seSBtaXNzaW5nIGZvciB0aGUgc2F0aXNmYWN0aW9uIHNjb3JlCmBgYHtyfQptaXNzaW5nLnZhbHVlcyA8LSBjbGFya3MgJT4lCiAgICBnYXRoZXIoa2V5ID0gImtleSIsIHZhbHVlID0gInZhbCIpICU+JQogICAgbXV0YXRlKGlzLm1pc3NpbmcgPSBpcy5uYSh2YWwpKSAlPiUKICAgIGdyb3VwX2J5KGtleSwgaXMubWlzc2luZykgJT4lCiAgICBzdW1tYXJpc2UobnVtLm1pc3NpbmcgPSBuKCkpICU+JQogICAgZmlsdGVyKGlzLm1pc3Npbmc9PVQpICU+JQogICAgc2VsZWN0KC1pcy5taXNzaW5nKSAlPiUKICAgIGFycmFuZ2UoZGVzYyhudW0ubWlzc2luZykpIAoKbWlzc2luZy52YWx1ZXMKYGBgCmBgYHtyfQptaXNzaW5nLnZhbHVlcyAlPiUKICBnZ3Bsb3QoKSArCiAgICBnZW9tX2JhcihhZXMoeD1rZXksIHk9bnVtLm1pc3NpbmcpLCBzdGF0ID0gJ2lkZW50aXR5JykgKwogICAgbGFicyh4PSd2YXJpYWJsZScsIHk9Im51bWJlciBvZiBtaXNzaW5nIHZhbHVlcyIsIHRpdGxlPSdOdW1iZXIgb2YgbWlzc2luZyB2YWx1ZXMnKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKYGBgCgoKCiMgQWRkIHNvbWUgbmV3IGNvbHVtbnMKYGBge3J9CmNsYXJrcyA8LSAgbXV0YXRlKGNsYXJrcywKICAgICAgICAgICAgICAgICAgdG90YWwuc3BlbmQgPSBvbmxpbmUuc3BlbmQgKyBzdG9yZS5zcGVuZCwKICAgICAgICAgICAgICAgICAgdG90YWwudHJhbnMgPSBvbmxpbmUudHJhbnMgKyBzdG9yZS50cmFucykgCiAKCmNsYXJrcyA8LSAgbXV0YXRlKGNsYXJrcywKICAgICAgICAgICAgICAgICAgcHJvcC5zcGVuZC5vbmxpbmUgPSBvbmxpbmUuc3BlbmQgLyAgdG90YWwuc3BlbmQsCiAgICAgICAgICAgICAgICAgIHByb3AudHJhbnMub25saW5lID0gb25saW5lLnRyYW5zIC8gdG90YWwudHJhbnMpIAoKCmNsYXJrcyA8LSAgbXV0YXRlKGNsYXJrcywKICAgICAgICAgICAgICAgICAgb25saW5lLnNwZW5kLnBlci50cmFucyA9IG9ubGluZS5zcGVuZCAvICBvbmxpbmUudHJhbnMsCiAgICAgICAgICAgICAgICAgIHN0b3JlLnNwZW5kLnBlci50cmFucyA9IHN0b3JlLnNwZW5kIC8gIHN0b3JlLnRyYW5zLAogICAgICAgICAgICAgICAgICB0b3RhbC5zcGVuZC5wZXIudHJhbnMgPSB0b3RhbC5zcGVuZCAvICB0b3RhbC50cmFucykgCmBgYAoKCgojIEluIHN0b3JlIHNwZW5kaW5nIGFjY291bnRzIGZvciAyMCUgb2YgdG90YWwKYGBge3J9CnRvdGFsX29ubGluZSA9IHN1bShjbGFya3Mkb25saW5lLnNwZW5kKQp0b3RhbF9zdG9yZSA9IHN1bShjbGFya3Mkc3RvcmUuc3BlbmQpCgp0b3RhbF9zdG9yZS8odG90YWxfb25saW5lK3RvdGFsX3N0b3JlKQpgYGAKCgojIHByb2ZpbGUgY3VzdG9tZXIgc3BlbmRpbmcgaW4gc3RvcmUgb3Igb25saW5lCmBgYHtyfQpjbGFya3Mkb25saW5lLnByb2ZpbGUgPC0gIk1peGVkIgpjbGFya3Mkb25saW5lLnByb2ZpbGVbY2xhcmtzJG9ubGluZS5zcGVuZCA9PSAwXSA8LSAiU3RvcmUgb25seSIKY2xhcmtzJG9ubGluZS5wcm9maWxlW2NsYXJrcyRzdG9yZS5zcGVuZCA9PSAwXSA8LSAiT25saW5lIG9ubHkiCmNsYXJrcyRvbmxpbmUucHJvZmlsZVtjbGFya3MkdG90YWwuc3BlbmQgPT0gMF0gPC0gIk5vIHNwZW5kIgpgYGAKCgoKI0NoYXJhY3RlcmlzdGljcyBvZiBvbmxpbmUgcHJvZmlsZSBpbmNsdWRpbmcgIk5vIFNwZW5kIiAgLSBubyBjb3JyZWxhdGlvbiB3aXRoIGFnZSwgY3JlZGl0LCBzYXRpc2ZhY3Rpb24uIERpc3RhbmNlIGlzIGludGVyZXN0aW5nIC0gaXQgbG9va3MgbGlrZSB0aGUgTm8gU3BlbmRlcnMgYW5kIHRoZSBPbmxpbmUgb25seSBwZW9wbGUgbGl2ZSBmdXJ0aGVyIGF3YXkuCmBgYHtyfQpjbGFya3MgJT4lIAogIGdyb3VwX2J5KG9ubGluZS5wcm9maWxlKSAlPiUKICBzdW1tYXJpc2Uobm9fcm93cyA9IGxlbmd0aChjdXN0LmlkKSkKCmNsYXJrcyAlPiUgCiAgZ3JvdXBfYnkob25saW5lLnByb2ZpbGUpICU+JQogIHN1bW1hcmlzZShtZWFuX3NwZW5kID0gbWVhbih0b3RhbC5zcGVuZCkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KG9ubGluZS5wcm9maWxlKSAlPiUKICBzdW1tYXJpc2UobWVhbl9hZ2UgPSBtZWFuKGFnZSkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KG9ubGluZS5wcm9maWxlKSAlPiUKICBzdW1tYXJpc2UobWVhbl9jcmVkaXQgPSBtZWFuKGNyZWRpdC5zY29yZSkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KG9ubGluZS5wcm9maWxlKSAlPiUKICBzdW1tYXJpc2UobWVkaWFuLmRpc3RhbmNlID0gbWVkaWFuKGRpc3RhbmNlLnRvLnN0b3JlKSkKCmZpbHRlcihjbGFya3MsIHNhdC5zZXJ2aWNlICE9ICJOQSIpICU+JSAKICBncm91cF9ieShvbmxpbmUucHJvZmlsZSkgJT4lCiAgc3VtbWFyaXNlKG1lYW4uc2F0LnNlciA9IG1lYW4oc2F0LnNlcnZpY2UpKQoKZmlsdGVyKGNsYXJrcywgc2F0LnNlbGVjdGlvbiAhPSAiTkEiKSAlPiUgCiAgZ3JvdXBfYnkob25saW5lLnByb2ZpbGUpICU+JQogIHN1bW1hcmlzZShtZWFuLnNhdC5zZWwgPSBtZWFuKHNhdC5zZWxlY3Rpb24pKQoKYGBgCgoKIyBEYXRhIHN1YnNldCBvZiBvbmx5IGN1c3RvbWVycyB3aG8gYWN0dWFsbHkgc3BlbmQgc29tZXRoaW5nCmBgYHtyfQpjbGFya3Muc3BlbmQgPC0gZmlsdGVyKGNsYXJrcyAsIG9ubGluZS5wcm9maWxlICE9ICJObyBzcGVuZCIpCmBgYAoKCgojQXJlIHRoZXJlIGFueSBkdXBsaWNhdGUgY3VzdG9tZXJzIHdobyBtYXkgYmUgc2FtZSBwZXJzb24gb24gYW5kIG9mZmxpbmU/CiNDaGVjayBkdXBsaWNhdGVzIGluIGRpc3RhbmNlLnRvLnN0b3JlPyAtIG9ubHkgb25lIGFuZCB0aGV5IGhhdmUgZGlmZmVyZW50IGFnZSBzbyBhbGwgZ29vZC4KYGBge3J9Cmxlbmd0aCh1bmlxdWUoY2xhcmtzJGRpc3RhbmNlLnRvLnN0b3JlKSkKCm5fb2NjdXIgPC0gZGF0YS5mcmFtZSh0YWJsZShjbGFya3MkZGlzdGFuY2UudG8uc3RvcmUpKQpjbGFya3NbY2xhcmtzJGRpc3RhbmNlLnRvLnN0b3JlICVpbiUgbl9vY2N1ciRWYXIxW25fb2NjdXIkRnJlcSA+IDFdLF0KCmBgYAoKCkZpcnN0IGxvb2sgYXQgY29ycmVsYXRpb25zOgpQbG90IHNob3dzIHBvc2l0aXZlIGNvcnJlbGF0aW9ucyBpbiByZWQgYW5kIG5lZ2F0aXZlIGluIGJsdWUuIEJpZ2dlciBjaXJjbGVzIHdpdGggc3Ryb2dlciBjb2xvdXIgaW5kaWNhdGUgc3Ryb25nZXIgY29yZWxhdGlvbnMuCmBgYHtyfQpkYXRhLm51bWVyaWMgPC0gbXV0YXRlKGNsYXJrcywgbnVtZXJpYy5lbWFpbCA9IGFzLm51bWVyaWMoZW1haWw9PSJ5ZXMiKSkKcnF1ZXJ5LmNvcm1hdChkYXRhLm51bWVyaWNbLGMoMyw0LDYsNyw4LDksMTAsMTEsMTIsMTMsMTUpXSkKYGBgCgpvbmxpbmUgc3BlbmQsIHZpc2l0cyBhbmQgdHJhbnNhY3Rpb25zIGFyZSBzdHJvbmdseSBwb3NpdGl2ZWx5IGNvcnJlbGF0ZXMKc3RvcmUgdHJhbnNhY3Rpb25zIGFuZCBzcGVuZCBhbHNvCgpzYXRpc2ZhY3Rpb24gd2l0aCBzZXJ2aWNlIGFuZCBzZWxlY3Rpb24gcG9zaXRpdmVseSBjb3JyZWxhdGVkCmFnZSBhbmQgY3JlZGl0IHNjb3JlIGxvb3NlbHkgY29yZWxhdGVkIChwb3NpdGl2ZSkKCndlYWsgbmVnYXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiBkaXN0YW5jZSB0byBzdG9yZSBhbmQgc3RvcmUgc3BlbmQgYW5kIHRyZWFuc2FjdGlvbnMKCnZlcnkgd2VhayBuZWdhdGl2ZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGFnZSBhbmQgb25saW5lIHZpc2l0L3RyYW5zL3NwZW5kIChvbGRlciBwZW9wbGUgc2xpZ2h0bHkgbGVzcyBsaWtlbHkgdG8gc2hvcCBvbmxpbmUpCgp0b3RhbCB0cmFuc2FjdGlvbiBpcyBmYXIgbW9yZSBsaW5rZWQgdG8gb25saW5lIHNwZW5kIC0gcHJvYmFibHkgYXMgc3RvcmUgc3BlbmQgaW4gb25seSAyMCUgb2YgdG90YWwKCgoKCiMgQXJlIG51bWJlciBvZiBvbmxpbmUgYW5kIHN0b3JlIHRyYW5zYWN0aW9ucyBwb3NpdGl2ZWx5IG9yIG5lZ2F0aXZlbHkgY29ycmVsYXRlZD8gLW5lZ2F0aXZlIGluIHRoZSBleHRyZW1lcyBidXQgbm8gbWFqb3IgY29ycmVsYXRpb24KYGBge3J9CiBnZ3Bsb3QoZGF0YSA9IGNsYXJrcywgYWVzKHggPSBzdG9yZS50cmFucywgeSA9IG9ubGluZS50cmFucykpICsKICAgICAgICAgIGdlb21fcG9pbnQoKSAKICAgICAgICAKICAgIGBgYAoKCgpQbG90IG9ubGluZSBhbmQgc3RvcmUgc3BlbmRzIC0gcmV2ZWFscyBzdHJhbmdlIGdhcCBpbiBvbmxpbmUgc3BlbmRpbmcgYXJvdW5kIMKjMjgKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGNsYXJrcy5zcGVuZCwgYWVzKHggPSBzdG9yZS5zcGVuZCwgeSA9IG9ubGluZS5zcGVuZCwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KCkgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpIApgYGAKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGNsYXJrcy5zcGVuZCwgYWVzKHggPSBzdG9yZS5zcGVuZCwgeSA9IG9ubGluZS5zcGVuZCwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KCkgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpICsKICAgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykgKyBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykKYGBgCgoKQXJlIHRyYW5zYWN0aW9ucyBhbmQgc3BlbmQgY29ycmVsYXRlZD8gIFlFUwpgYGB7cn0KIGdncGxvdChkYXRhID0gY2xhcmtzLCBhZXMoeCA9IHRvdGFsLnRyYW5zLCB5ID0gdG90YWwuc3BlbmQpKSArCiAgICAgICAgICBnZW9tX3BvaW50KCkKYGBgCgoKQXJlIHByb3Agb25saW5lIGFuZCB0b3RhbCB0cmFucy9zcGVuZCBjb3JyZWxhdGVkPyAgLSBZZXMsIHBhcnRpY3Vhcmx5IGF0IHRoZSB1cHBlciBlbmQKYGBge3J9CiAKZmlsdGVyKGNsYXJrcywgIWlzLm5hbihwcm9wLnNwZW5kLm9ubGluZSkpICU+JQpnZ3Bsb3QoIGFlcyh4ID0gcHJvcC5zcGVuZC5vbmxpbmUsIHkgPSB0b3RhbC5zcGVuZCkpICsKICAgICAgICAgIGdlb21fcG9pbnQoKQpgYGAKYGBge3J9CiAKZmlsdGVyKGNsYXJrcywgIWlzLm5hbihwcm9wLnRyYW5zLm9ubGluZSkpICU+JQpnZ3Bsb3QoIGFlcyh4ID0gcHJvcC50cmFucy5vbmxpbmUsIHkgPSB0b3RhbC50cmFucykpICsKICAgICAgICAgIGdlb21fcG9pbnQoKQpgYGAKCgoKCiMgT25saW5lIHByb2ZpbGUKT25saW5lIG9ubHkgcGVvcGxlIHNlZW0gdG8gc3BlbmQgYSBsb3QgbGVzcyBQRVIgVFJBTlNBQ1RJT04gYW5kIHdpdGhpbiBhIG11Y2ggc21hbGxlciByYW5nZSAoZS5nLiBmb3Igc2hvZXMgb25seSBidXkgb25lIHBhaXIgYXQgYSB0aW1lKSAtIGluY2VudGl2aXNlIGJ1eSBvbmUgZ2V0IG9uZSAyMCUgb2ZmIGFzIHRoZXkgYWRkIHRvIGNhcnQ/CgpgYGB7cn0KY2xhcmtzLnNwZW5kICU+JQogIGdncGxvdCggYWVzKHggPSBvbmxpbmUucHJvZmlsZSwgeSA9IHRvdGFsLnNwZW5kLnBlci50cmFucywgZ3JvdXAgPSBvbmxpbmUucHJvZmlsZSkpICsgCiAgICBnZW9tX2ppdHRlciggYWxwaGE9MC4wNSAsIGNvbG91ciA9ICJyZWQiICkgICArCiAgICBnZW9tX2JveHBsb3QoIGNvbG91ciA9ICJibHVlIiAsIGFscGhhPTApICAKYGBgCgoKYGBge3J9CnBpdm90X2xvbmdlcihjbGFya3Muc3BlbmQsIGNvbHMgPSBzdG9yZS5zcGVuZC5wZXIudHJhbnM6b25saW5lLnNwZW5kLnBlci50cmFucywgbmFtZXNfdG8gPSAid2hlcmUiLCB2YWx1ZXNfdG8gPSAiYXZlLnNwZW5kLnBlci50cmFucyIpICU+JQpnZ3Bsb3QoYWVzKHggPSB3aGVyZSx5ID0gYXZlLnNwZW5kLnBlci50cmFucyksIGdyb3VwID0gd2hlcmUpICsgCiAgICBnZW9tX2ppdHRlciggYWxwaGE9MC4wNSAsIGNvbG91ciA9ICJyZWQiICkgICArCiAgICBnZW9tX2JveHBsb3QoIGNvbG91ciA9ICJibHVlIiAsIGFscGhhPTApICAKYGBgCgoKClN0b3JlIG9ubHkgcGVvcGxlIHRlbmQgbm90IHRvIHNwZW5kIGFzIG11Y2ggT1ZFUkFMTCAtIGNhbiB3ZSBnZXQgdGhlbSB0byBiZSByZXR1cm5pbmcgY3VzdG9tZXJzPyBPZmZlciBpbiBiYWcgd2l0aCBwdXJjaGFzZT8gRm9yIHJldHVybmluZyB0byBzdG9yZSBvciBvbmxpbmU/IENvbGxlY3QgZW1haWwgYWRkcmVzc2VzIGluIHN0b3JlPyBEbyB3ZSBhbHJlYWR5IGFzaz8gSG93IGhpZ2ggaXMgdGFrZSB1cCByYXRlPwoKYGBge3J9CmNsYXJrcy5zcGVuZCAlPiUKICBnZ3Bsb3QoIGFlcyh4ID0gb25saW5lLnByb2ZpbGUsIHkgPSB0b3RhbC5zcGVuZCwgZ3JvdXAgPSBvbmxpbmUucHJvZmlsZSkpICsKICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkrCiAgIGdlb21faml0dGVyKCBhbHBoYT0wLjA1ICwgY29sb3VyID0gInJlZCIgKSAgICsKICAgIGdlb21fYm94cGxvdCggY29sb3VyID0gImJsdWUiICwgYWxwaGE9MCkgIApgYGAKCmBgYHtyfQoKc2VsZWN0KGNsYXJrcy5zcGVuZCwgc3RvcmUuc3BlbmQsb25saW5lLnNwZW5kKSU+JQpwaXZvdF9sb25nZXIoIGNvbHMgPSAxOjIsIG5hbWVzX3RvID0gIndoZXJlIiwgdmFsdWVzX3RvID0gInNwZW5kIikgJT4lCmdncGxvdChhZXMoeCA9IHdoZXJlLHkgPSBzcGVuZCksIGdyb3VwID0gd2hlcmUpICsgCiAgICBnZW9tX2ppdHRlciggYWxwaGE9MC4wNSAsIGNvbG91ciA9ICJyZWQiICkgICArCiAgICBnZW9tX2JveHBsb3QoIGNvbG91ciA9ICJibHVlIiAsIGFscGhhPTApICAKYGBgCgoKCkVtYWlsIGlzIHNsaWdodGx5IGxvd2VyIGZvciBzdG9yZSBvbmx5LCBidXQgYWN0dWFsbHkgcXVpdGUgbG93IHRocm91Z2hvdXQgLSBwZXJoYXBzIHdlIGNhbiBlbmNvdXJhZ2UgZW1haWwgc2hhcmluZyBhbmQgY29sbGVjdCBkYXRhIHRvIHNlZSBob3cgc3VjY2Vzc2Z1bCB0aGlzIGlzLiBDb21wZXRpdGlvbiBvciBvZmZlciB0byBnZXQgcGVvcGxlIHRvIHNpZ24gdXA/CgpgYGB7cn0KY2xhcmtzLnNwZW5kICU+JQpnZ3Bsb3QoYWVzKG9ubGluZS5wcm9maWxlKSkgKyAKICBnZW9tX2JhcihhZXMoZmlsbCA9IGFzLmZhY3RvcihlbWFpbCkpLCBwb3NpdGlvbiA9ICJmaWxsIikKYGBgCgoKCgoKIyBMb29rIGF0IHNhdGlzZmFjdGlvbiAKVGhpcyBpcyBub3QgdmVyeSBjb3JyZWxhdGVkIHRvIGFueXRoaW5nIG90aGVyIHRoYW4gdGhlIHR3byBtZXRyaWNzIGJlaW5nIGNvcnJlbGF0ZWQgd2l0aCBlYWNoIG90aGVyCgpgYGB7cn0KY2xhcmtzLnNwZW5kICU+JQogIGdncGxvdCggYWVzKHggPSBvbmxpbmUucHJvZmlsZSwgeSA9IHNhdC5zZWxlY3Rpb24sIGdyb3VwID0gb25saW5lLnByb2ZpbGUpKSArCiAgICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpKwogICAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjAxICwgY29sb3VyID0gImJsdWUiICkgKwogICAgZ2VvbV9ib3hwbG90KGFscGhhID0gMCkgIApgYGAKCkhvdyBkbyB0aGV5IGhhdmUgc2F0aXNmYWN0aW9uIHNjb3JlcyBmb3Igbm8gc3BlbmQgcGVvcGxlPwpgYGB7cn0KY2xhcmtzW2NsYXJrcyRzYXQuc2VydmljZSAhPSAiTkEiLF0gJT4lCmdncGxvdChhZXMob25saW5lLnByb2ZpbGUpKSArIAogIGdlb21fYmFyKGFlcyhmaWxsID0gYXMuZmFjdG9yKHNhdC5zZXJ2aWNlKSksIHBvc2l0aW9uID0gImZpbGwiKQpgYGAKCiMgQWdlCmBgYHtyfQogZ2dwbG90KGRhdGEgPSBjbGFya3Muc3BlbmQsIGFlcyh4ID0gYWdlLCB5ID0gdG90YWwuc3BlbmQsIGNvbG91ciA9IG9ubGluZS5wcm9maWxlKSkgKwogICAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpICsKICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9J2xvZzInKSAKYGBgCgoKYGBge3J9CiBnZ3Bsb3QoZGF0YSA9IGNsYXJrcy5zcGVuZCwgYWVzKHggPSBhZ2UsIHkgPSB0b3RhbC5zcGVuZC5wZXIudHJhbnMsIGNvbG91ciA9IG9ubGluZS5wcm9maWxlKSkgKwogICAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpIApgYGAKSXQgc2VlbXMgdGhlcmUgaXMgbGl0dGxlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGFnZSBhbmQgVE9UQUwgU1BFTkQsIGJ1dCBvbGQgYW5kIHlvdW5nIHNwZW5kIGxlc3MgUEVSIFRSQU5TQUNUSU9OLiBQZXJoYXBzIHRob3NlIGluIDMwcyBidXkgZm9yIHRoZWlyIHdob2xlIGZhbWlseSBhdCBvbmNlIGJ1dCBjYW4ndCBidWRnZXQgbW9yZSBvdmVyYWxsLiBPciB0aGV5IGhhdmUgbGVzcyB0aW1lIHRvIHNob3Agc28gYnV5IGFsbCBhdCBvbmNlLgoKCgoKYGBge3J9CgpmaWx0ZXIoY2xhcmtzLnNwZW5kICwgb25saW5lLnByb2ZpbGUgIT0gIlN0b3JlIG9ubHkiKSAlPiUKIGdncGxvdChhZXMoeCA9IGFnZSwgeSA9IHRvdGFsLnNwZW5kLnBlci50cmFucywgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkgCmBgYAoKCgoKCgojIENyZWRpdCBzY29yZQoKYGBge3J9CiBnZ3Bsb3QoZGF0YSA9IGNsYXJrcy5zcGVuZCwgYWVzKHggPSBjcmVkaXQuc2NvcmUsIHkgPSB0b3RhbC5zcGVuZCwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSsgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGFscGhhID0gMSkpKSArCiBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9J2xvZzInKQpgYGAKCmBgYHtyfQogZ2dwbG90KGRhdGEgPSBjbGFya3Muc3BlbmQsIGFlcyh4ID0gY3JlZGl0LnNjb3JlLCB5ID0gdG90YWwuc3BlbmQucGVyLnRyYW5zLCBjb2xvdXIgPSBvbmxpbmUucHJvZmlsZSkpICsKICAgICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEpKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpICsKIHNjYWxlX3lfY29udGludW91cyh0cmFucz0nbG9nMicpCmBgYAoKIyBkaXN0YW5jZSB0byBzdG9yZQoKYGBge3J9CmZpbHRlcihjbGFya3Muc3BlbmQsIGRpc3RhbmNlLnRvLnN0b3JlIDwgMC45OSogbWF4KGRpc3RhbmNlLnRvLnN0b3JlKSkgJT4lCgpnZ3Bsb3QoIGFlcyh4ID0gZGlzdGFuY2UudG8uc3RvcmUsIHkgPSB0b3RhbC5zcGVuZCwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkgKwogICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykgKyBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9J2xvZzInKQpgYGAKCkNhbiBzZWUgd2hhdCB5b3Ugd291bGQgZXhwZWN0IGhlcmUgLSB0aG9zZSBjbG9zZXIgdG8gc3RvcmUgbW9yZSBsaWtlbHkgdG8gc3BlbmQgaW4gc3RvcmUuIHRob3NlIGZhciBhd2F5IG1vcmUgbGlrZWx5IHRvIHNob3Agb25saW5lIG9ubHkuCgoKCgojIEJlbG93IGFyZSBsb3RzIG1vcmUgcG90cyB3aGljaCBtYXkgYmUgb2YgaW50ZXJlc3QgaW4gZnV0dXJlIHdpdGggZGlmZmVyZW50IGRhdGEgYnV0IGRpZCBub3QgeWllbGQgbXVjaCBpbnNpZ2h0CgpgYGB7cn0KIGdncGxvdChkYXRhID0gY2xhcmtzLCBhZXMoeCA9IG9ubGluZS52aXNpdHMsIHkgPSB0b3RhbC5zcGVuZCwgIGNvbG91ciA9IG9ubGluZS5wcm9maWxlKSkgKwogICAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpCmBgYAoKCmBgYHtyfQogZ2dwbG90KGRhdGEgPSBjbGFya3MsIGFlcyh4ID0gb25saW5lLnZpc2l0cywgeSA9IG9ubGluZS5zcGVuZCwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkKYGBgCmBgYHtyfQogZ2dwbG90KGRhdGEgPSBjbGFya3MsIGFlcyh4ID0gb25saW5lLnZpc2l0cywgeSA9IHN0b3JlLnNwZW5kLCBjb2xvdXIgPSBvbmxpbmUucHJvZmlsZSkpICsKICAgICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEpICsgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGFscGhhID0gMSkpKQpgYGAKCgoKCgoKYGBge3J9CiBnZ3Bsb3QoZGF0YSA9IGNsYXJrcywgYWVzKHggPSBzdG9yZS50cmFucywgeSA9IHN0b3JlLnNwZW5kLCBjb2xvdXIgPSBwcm9wLnNwZW5kLm9ubGluZSkpICsKICAgICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEpCmBgYAoKYGBge3J9CiBnZ3Bsb3QoZGF0YSA9IGNsYXJrcywgYWVzKHggPSBzdG9yZS50cmFucywgeSA9IG9ubGluZS5zcGVuZCwgY29sb3VyID0gcHJvcC5zcGVuZC5vbmxpbmUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKQpgYGAKCgpgYGB7cn0KIGdncGxvdChkYXRhID0gY2xhcmtzLCBhZXMoeCA9IHN0b3JlLnRyYW5zLCB5ID0gdG90YWwuc3BlbmQsIGNvbG91ciA9IHByb3Auc3BlbmQub25saW5lKSkgKwogICAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkKYGBgCgoKCgoKIEFyZSBvbGRlciBwZW9wbGUgbW9yZSBzYXRpc2ZpZWQ/IAogCmBgYHtyfQpjbGFya3MgJT4lCm11dGF0ZShhZ2UuZ3JvdXA9Y3V0KGFnZSwgYnJlYWtzPWMoMCwgMjAsIDMwLCA0MCwgNTAsIDEwMCksIGxhYmVscz1jKCI8MjAiLCIyMHMiLCIzMHMiLCI0MHMiLCI1MHMiKSkpICU+JQogIGdyb3VwX2J5KGFnZS5ncm91cCkgJT4lCiAgZmlsdGVyKHNhdC5zZXJ2aWNlICE9ICJOQSIpICU+JQogICAgc3VtbWFyaXplKG1lYW4uc2F0aXNmYWN0aW9uID0gbWVhbihzYXQuc2VydmljZSkpCgpgYGAKIAoKIAogIEFyZSBvbGRlciBwZW9wbGUgc3BlbmRpbmcgbGVzcyBwZXIgdHJhbnNhY3Rpb24/IEkgZGlkIGxvb2sgdGhpcyB3YXksIGJ1dCBhY3R1YWxseSBqdXN0IGZld2VyIGRhdGEgcG9pbnRzIGluIHRoZXNlIGNhdGVnb3JpZXMuCiAKYGBge3J9CmNsYXJrcyAlPiUKbXV0YXRlKGFnZS5ncm91cD1jdXQoYWdlLCBicmVha3M9YygwLCAyMCwgMzAsIDQwLCA1MCwgMTAwKSwgbGFiZWxzPWMoIjwyMCIsIjIwcyIsIjMwcyIsIjQwcyIsIjUwcyIpKSkgJT4lCiAgZ2dwbG90KCBhZXMoeCA9IGFnZS5ncm91cCwgeSA9IHRvdGFsLnNwZW5kLnBlci50cmFucywgZ3JvdXAgPSBhZ2UuZ3JvdXAsIGNvbG91ciA9IG9ubGluZS5wcm9maWxlKSkgKwogICAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGFscGhhID0gMSkpKSsKICAgIGdlb21faml0dGVyKGFscGhhID0gMC4xICkgKwogICAgZ2VvbV9ib3hwbG90KGFscGhhID0gMCkgIApgYGAKIAogIEFyZSBvbGRlciBwZW9wbGUgc3BlbmRpbmcgbW9yZSBtb25leT8KIApgYGB7cn0KY2xhcmtzICU+JQptdXRhdGUoYWdlLmdyb3VwPWN1dChhZ2UsIGJyZWFrcz1jKDAsIDIwLCAzMCwgNDAsIDUwLCAxMDApLCBsYWJlbHM9YygiPDIwIiwiMjBzIiwiMzBzIiwiNDBzIiwiNTBzIikpKSAlPiUKICBnZ3Bsb3QoIGFlcyh4ID0gYWdlLmdyb3VwLCB5ID0gdG90YWwuc3BlbmQsIGdyb3VwID0gYWdlLmdyb3VwLCBjb2xvdXIgPSBvbmxpbmUucHJvZmlsZSkpICsKICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkrCiAgICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMSApICsKICAgIGdlb21fYm94cGxvdChhbHBoYSA9IDApICAKYGBgCiAKICBBcmUgb2xkZXIgcGVvcGxlIHNwZW5kaW5nIG1vcmUgZnJlcXVlbnRseT8KIApgYGB7cn0KY2xhcmtzLnNwZW5kICU+JQptdXRhdGUoYWdlLmdyb3VwPWN1dChhZ2UsIGJyZWFrcz1jKDAsIDIwLCAzMCwgNDAsIDUwLCAxMDApLCBsYWJlbHM9YygiPDIwIiwiMjBzIiwiMzBzIiwiNDBzIiwiNTBzIikpKSAlPiUKICBnZ3Bsb3QoIGFlcyh4ID0gYWdlLmdyb3VwLCB5ID0gdG90YWwudHJhbnMsIGdyb3VwID0gYWdlLmdyb3VwLCBjb2xvdXIgPSBvbmxpbmUucHJvZmlsZSkpICsKICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkrCiAgICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMSApICsKICAgIGdlb21fYm94cGxvdChhbHBoYSA9IDApICAKYGBgCgoKICMgU2F0aXNmYWN0aW9uIGFzIGEgaW5wdXQ/IAogCmBgYHtyfQpjbGFya3MgJT4lIAogIGdyb3VwX2J5KHNhdC5zZXJ2aWNlKSAlPiUKICBzdW1tYXJpc2Uobm9fcm93cyA9IGxlbmd0aChjdXN0LmlkKSkKCmNsYXJrcyAlPiUgCiAgZ3JvdXBfYnkoc2F0LnNlcnZpY2UpICU+JQogIHN1bW1hcmlzZShtZWFuX3NwZW5kID0gbWVhbih0b3RhbC5zcGVuZCkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KHNhdC5zZXJ2aWNlKSAlPiUKICBzdW1tYXJpc2UobWVhbl9hZ2UgPSBtZWFuKGFnZSkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KHNhdC5zZXJ2aWNlKSAlPiUKICBzdW1tYXJpc2UobWVhbl9jcmVkaXQgPSBtZWFuKGNyZWRpdC5zY29yZSkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KHNhdC5zZXJ2aWNlKSAlPiUKICBzdW1tYXJpc2UobWVkaWFuLmRpc3RhbmNlID0gbWVkaWFuKGRpc3RhbmNlLnRvLnN0b3JlKSkKCmZpbHRlcihjbGFya3MsIHNhdC5zZWxlY3Rpb24gIT0gIk5BIikgJT4lIAogIGdyb3VwX2J5KHNhdC5zZXJ2aWNlKSAlPiUKICBzdW1tYXJpc2UobWVhbi5zYXQuc2VsID0gbWVhbihzYXQuc2VsZWN0aW9uKSkKYGBgCgpUaGVyZSBkb2Vzbid0IGFwcGVhciB0byBiZSBhbnkgY29ycmVsYXRpb24gYmV0d2VlbiBzYXRpc2ZhY3Rpb24gb3Igc2VydmljZSBhbmQgYW55IG90aGVyIHZhcmlhYmxlcywgZXhjZXB0IGZvciBzYXRpc2ZhY3Rpb24gb2Ygc2VsZWN0aW9uIHdoaWNoIHlvdSB3b3VsZCBleHBlY3QgaGlnaCBzY29yZXJzIHdvdWxkIHNjb3JlIGhpZ2ggb24gYm90aAogCiAKIEFyZSBtb3JlIHNhdGlzZmllZCBwZW9wbGUgbWFraW5nIG1vcmUgcHVyY2hhc2VzPwpgYGB7cn0KCmZpbHRlcihjbGFya3MsIHNhdC5zZXJ2aWNlICE9ICJOQSIpICU+JQpnZ3Bsb3QoIGFlcyh4ID0gc2F0LnNlcnZpY2UsIHkgPSB0b3RhbC50cmFucywgZ3JvdXAgPSBzYXQuc2VydmljZSwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpKwogICAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjEgKSArCiAgICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwKSAgCmBgYAogCiAKIAogICBBcmUgbW9yZSBzYXRpc2ZpZWQgcGVvcGxlIHNwZW5kaW5nIG1vcmU/CmBgYHtyfQoKZmlsdGVyKGNsYXJrcywgc2F0LnNlcnZpY2UgIT0gIk5BIikgJT4lCmdncGxvdCggYWVzKHggPSBzYXQuc2VydmljZSwgeSA9IHRvdGFsLnNwZW5kLCBncm91cCA9IHNhdC5zZXJ2aWNlLCBjb2xvciA9IHByb3Auc3BlbmQub25saW5lKSkgKwogICAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjEgKSArCiAgICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwKSAgCmBgYAoKCmBgYHtyfQpmaWx0ZXIoY2xhcmtzLCBzYXQuc2VsZWN0aW9uICE9ICJOQSIpICU+JQpnZ3Bsb3QoIGFlcyh4ID0gc2F0LnNlbGVjdGlvbiwgeSA9IHRvdGFsLnNwZW5kLCBncm91cCA9IHNhdC5zZWxlY3Rpb24sIGNvbG9yID0gcHJvcC5zcGVuZC5vbmxpbmUpKSArCiAgICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMSApICsKICAgIGdlb21fYm94cGxvdChhbHBoYSA9IDApICAKYGBgCgoKCmBgYHtyfQpmaWx0ZXIoY2xhcmtzLCBzYXQuc2VydmljZSAhPSAiTkEiKSAlPiUKZmlsdGVyKCAhaXMubmFuKHByb3Auc3BlbmQub25saW5lKSkgJT4lCmdncGxvdCggYWVzKHggPSBwcm9wLnNwZW5kLm9ubGluZSwgeSA9IHNhdC5zZXJ2aWNlKSkgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSApIAoKYGBgCmBgYHtyfQoKZmlsdGVyKGNsYXJrcywgc2F0LnNlcnZpY2UgIT0gIk5BIikgJT4lCmdncGxvdCggYWVzKHggPSBzYXQuc2VydmljZSwgeSA9ICBwcm9wLnNwZW5kLm9ubGluZSwgZ3JvdXAgPSBzYXQuc2VydmljZSkpICsKICAgIGdlb21faml0dGVyKGFscGhhID0gMC4wNSApICsKICAgIGdlb21fdmlvbGluKGFscGhhID0gMCwgY29sb3VyID0gInJlZCIpICAKYGBgCgpgYGB7cn0KCmZpbHRlcihjbGFya3MsIHNhdC5zZWxlY3Rpb24gIT0gIk5BIikgJT4lCmdncGxvdCggYWVzKHggPSBzYXQuc2VsZWN0aW9uLCB5ID0gIHByb3Auc3BlbmQub25saW5lLCBncm91cCA9IHNhdC5zZWxlY3Rpb24pKSArCiAgICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMDUgKSArCiAgICBnZW9tX3Zpb2xpbihhbHBoYSA9IDAsIGNvbG91ciA9ICJyZWQiKSAgCmBgYAoKCgpgYGB7cn0KY2xhcmtzJT4lCmdyb3VwX2J5KHNhdC5zZWxlY3Rpb24pJT4lCnN1bW1hcml6ZShtZWFuLnNwZW5kID0gbWVhbih0b3RhbC5zcGVuZCkpJT4lCiAgZ2dwbG90KCBhZXMoeD1zYXQuc2VsZWN0aW9uLCB5PW1lYW4uc3BlbmQpKSArIAogIGdlb21fY29sKCkKYGBgCgoKYGBge3J9CmNsYXJrcyU+JQpncm91cF9ieShzYXQuc2VydmljZSklPiUKc3VtbWFyaXplKG1lYW4uc3BlbmQgPSBtZWFuKHRvdGFsLnNwZW5kKSklPiUKICBnZ3Bsb3QoIGFlcyh4PXNhdC5zZXJ2aWNlLCB5PW1lYW4uc3BlbmQpKSArIAogIGdlb21fY29sKCkKYGBgCgoKCmBgYHtyfQoKZmlsdGVyKGNsYXJrcywgc2F0LnNlbGVjdGlvbiAhPSAiTkEiKSAlPiUKZ2dwbG90KCBhZXMoeCA9IHNhdC5zZWxlY3Rpb24pKSArCiAgICBnZW9tX2JhciggKSAKICAKYGBgCgoKYGBge3J9CmZpbHRlcihjbGFya3MsIHNhdC5zZWxlY3Rpb24gIT0gIk5BIikgJT4lCmdncGxvdCggYWVzKHggPSBzYXQuc2VydmljZSkpICsKICAgIGdlb21fYmFyKCApIAogIApgYGAKCmBgYHtyfQpjbGFya3MlPiUKbXV0YXRlKG5vLm9ubGluZS52aXNpdHMgPSBvbmxpbmUudmlzaXRzPT0wKSAlPiUgCmdncGxvdCggYWVzKHggPSBuby5vbmxpbmUudmlzaXRzLCB5ID0gdG90YWwuc3BlbmQsIGdyb3VwID0gbm8ub25saW5lLnZpc2l0cywgY29sb3VyID0gc2F0LnNlcnZpY2UpKSArCiAgICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMDUgKSArCiAgICBnZW9tX3Zpb2xpbihhbHBoYSA9IDAsIGNvbG91ciA9ICJyZWQiKSAgCmBgYAoKYGBge3J9CmNsYXJrcy5zcGVuZCU+JQpmaWx0ZXIob25saW5lLnByb2ZpbGUgIT0gIk5vbmUiKSAlPiUKZ2dwbG90KCBhZXMoeCA9IG9ubGluZS5wcm9maWxlLCB5ID0gdG90YWwuc3BlbmQsIGdyb3VwID0gb25saW5lLnByb2ZpbGUsIGNvbG91ciA9IHNhdC5zZXJ2aWNlKSkgKwogICAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjA1ICkgKwogICAgZ2VvbV92aW9saW4oYWxwaGEgPSAwLCBjb2xvdXIgPSAicmVkIikgIApgYGAKCgoKYGBge3J9CmNsYXJrcy5zcGVuZCU+JQpmaWx0ZXIob25saW5lLnByb2ZpbGUgIT0gIk5vbmUiKSAlPiUKZ2dwbG90KCBhZXMoeCA9IG9ubGluZS5wcm9maWxlLCB5ID0gZGlzdGFuY2UudG8uc3RvcmUsIGdyb3VwID0gb25saW5lLnByb2ZpbGUsIGNvbG91ciA9IHNhdC5zZXJ2aWNlKSkgKwogICAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjA1ICkgKwogICAgZ2VvbV92aW9saW4oYWxwaGEgPSAwLCBjb2xvdXIgPSAicmVkIikgIApgYGAKCgoKCmBgYHtyfQpjbGFya3MlPiUKZmlsdGVyKHN0b3JlLnNwZW5kPjApJT4lCmdncGxvdCggYWVzKHggPSBkaXN0YW5jZS50by5zdG9yZSwgeSA9IHN0b3JlLnNwZW5kLCBjb2xvdXIgPSBzYXQuc2VydmljZSkpICsKZ2VvbV9wb2ludChhbHBoYT0wLjEpCmBgYAoKCgpgYGB7cn0KY2xhcmtzJT4lCm11dGF0ZShzdG9yZSA9IHN0b3JlLnNwZW5kID4gMCklPiUKZ3JvdXBfYnkoc3RvcmUpICU+JQpzdW1tYXJpemUobWVhbl9zcGVuZCA9IG1lYW4ob25saW5lLnNwZW5kKSkKCmBgYAoKYGBge3J9CmNsYXJrcyU+JQptdXRhdGUoc3RvcmUgPSBzdG9yZS5zcGVuZCA+IDApJT4lCmdncGxvdChhZXMoeD1zdG9yZSwgeT10b3RhbC5zcGVuZCAsIGdyb3VwPXN0b3JlKSkrCiAgZ2VvbV92aW9saW4oKQoKYGBgCgoKCgo=